<?php

/**
 * 阿里支付接口
 *
 * Author: bluer
 * Date: 5/25/16
 */

require CONFIG_DIR . '/pay.config.php';

class Alipay {
    protected $_wapPayReqId; //请求号，须保证每次请求都是唯一
    protected $_bank;
    protected static $_wapPayUrl;
    protected static $_payUrl;
    protected static $_wapPayCertPath;
    protected static $_publicKeyPath;

    function __construct() {
        $this->_wapPayReqId = time();
        self::$_wapPayUrl = 'http://wappaygw.alipay.com/service/rest.htm?_input_charset=utf-8&';
        self::$_payUrl = 'https://mapi.alipay.com/gateway.do?_input_charset=utf-8&';
        self::$_wapPayCertPath = __DIR__ . '/keyfiles/alipay_sign_cacert.pem';
        self::$_publicKeyPath = CONFIG_DIR . '/keyfiles/appub_alipay_public_key.pem';
    }

    /**
     * 设置银行简写
     * @param string $bank 银行简写
     */
    public function setBank($bank) {
        $this->_bank = $bank;
    }

    /**
     * 设置CA证书路径
     * @param $path
     */
    public function setWapPayCertPath($path) {
        self::$_wapPayCertPath = $path;
    }

    /**
     * 设置RSA公钥路径
     * @param $path
     */
    public function setPublicKeyPath($path) {
        self::$_publicKeyPath = $path;
    }

    private function buildPayArray($orderId, $productName, $price, $validPeriod, $isUnionPay = false) {
        //构造要请求的参数数组，无需改动
        $para = array(
            'service' => 'create_direct_pay_by_user',
            'partner' => ALIPAY_PARTNER,
            'seller_email' => ALIPAY_SELLER,
            'payment_type' => '1',
            'notify_url' => ALIPAY_DIRECT_WEB_NOTIFY_URL,
            'return_url' => ALIPAY_DIRECT_WEB_CALLBACK_URL,
            'out_trade_no' => $orderId,
            'subject' => $productName,
            'total_fee' => $price
        );

        if ($isUnionPay) {
            $para['paymethod'] = 'bankPay';
            $para['defaultbank'] = $this->_bank;
        }

        $para['it_b_pay'] = $validPeriod . 'm';
        $para['_input_charset'] = 'utf-8';
        $para['sign_type'] = 'MD5';

        return $para;
    }

    /**
     * 获取网银支付需要的数据
     * @param int $orderId 订单号
     * @param string $productName 订单名称
     * @param float $price 价格
     * @param int $validPeriod 有效时长
     * @return mixed
     */
    public function bankPay($orderId, $productName, $price, $validPeriod) {
        $para = $this->buildPayArray($orderId, $productName, $price, $validPeriod, true);
        $para = $this->buildRequestPara($para);

        return self::$_payUrl . $this->array2query($para, true); //最终的请求地址
    }

    /**
     * 获取web支付需要的数据
     * @param int $orderId 订单号
     * @param string $productName 订单名称
     * @param float $price 价格
     * @param int $validPeriod 有效时长
     * @return mixed
     */
    function pay($orderId, $productName, $price, $validPeriod) {
        $para = $this->buildPayArray($orderId, $productName, $price, $validPeriod);
        $para = $this->buildRequestPara($para);

        return self::$_payUrl . $this->array2query($para, true);
    }

    /**
     * 获取wap支付需要的数据
     * @param int $orderId 订单号
     * @param string $productName 订单名称
     * @param float $price 价格
     * @return mixed
     */
    public function wapPay($orderId, $productName, $price) {
        //支付前获取access_token
        $accessToken = $this->getAccessToken($orderId, $productName, $price);

        if (empty($accessToken))
            return '';

        //构造要请求的参数数组
        $reqData = '<auth_and_execute_req><request_token>'.$accessToken.'</request_token></auth_and_execute_req>';
        $para = array(
            "service" => 'alipay.wap.auth.authAndExecute',
            "partner" => ALIPAY_PARTNER,
            "sec_id" => 'MD5',
            "format" => 'xml',
            "v"	=> '2.0',
            "req_id" => $this->_wapPayReqId,
            "req_data" => $reqData,
            "_input_charset" => 'utf-8'
        );
        $para = $this->buildRequestPara($para);

        return self::$_wapPayUrl . $this->array2query($para); //最终的请求地址
    }

    /**
     * 检验回调的签名有效性
     * @param string $algo 加密算法名称. md5 | rsa
     * @param array $para 参数
     * @param bool $isSort 是否对待签名数组排序
     * @return bool
     */
    public function checkSign($algo, $para, $isSort) {
        if ($algo == 'md5') return $this->checkMD5($para, $isSort);
        if ($algo == 'rsa') return $this->checkRSA($para);

        return false;
    }

    /**
     * 检验回调的签名有效性
     * @param array $para 参数
     * @param bool $isSort 是否对待签名数组排序
     * @return mixed
     */
    public function checkMD5($para, $isSort) {
        $sign = $para['sign'];
        unset($para['sign']);
 
        if ($isSort) {
            ksort($para);
        } else {
            // wap异步通知时，对参数做固定排序
            $para = array(
                'service' => $para['service'],
                'v' => $para['v'],
                'sec_id' => $para['sec_id'],
                'notify_data' => $para['notify_data']
            );
        }

        $str = $this->array2query($para);

        return $sign == md5($str . ALIPAY_DIRECT_KEY);
    }

    /**
     * 校验rsa签名
     *
     * @param $queryArgs
     * @return bool
     */
    protected function checkRSA($queryArgs) {
        $args = array();
        foreach ($queryArgs as $key => $arg) {
            if ($key == 'sign' || $key == 'sign_type' || $arg == '')
                continue;

            $args[$key] = $key . '=' . $arg;
        }

        ksort($args);
        $paramStr = implode('&', $args);

        return $this->rsaVerify($paramStr, $queryArgs['sign']);
    }

    /**
     * RSA验签
     * @param string $data 待签名数据
     * @param string $sign 要校对的的签名结果
     * @return bool
     */
    protected function rsaVerify($data, $sign)  {
        $pubKey = file_get_contents(self::$_publicKeyPath);
        $res = openssl_get_publickey($pubKey);
        $result = openssl_verify($data, base64_decode($sign), $res);
        openssl_free_key($res);

        return $result === 1;
    }

    /**
     * 生成要请求给支付宝的参数数组
     * @param array $para 请求前的参数数组
     * @return array 要请求的参数数组
     */
    protected function buildRequestPara($para) {
        //对待签名参数数组排序
        ksort($para);

        $str = $this->array2query($para);
        //生成签名结果
        $sign = md5($str . ALIPAY_DIRECT_KEY);

        //签名结果与签名方式加入请求提交参数组中
        $para['sign'] = $sign;

        return $para;
    }

    /**
     * 数组转成请求字符串
     * @param array $para 参数数组
     * @param bool $urlencode 是否对参数进行url编码
     * @return string
     */
    protected function array2query($para, $urlencode = false)
    {
        $str = '';
        foreach ($para as $key => $value) {
            if ($key == "sign_type" || $value == "")
                continue;

            $value2 = $urlencode ? urlencode($value) : $value;
            $str .= ($key . '=' . $value2 . '&');

        }

        return rtrim($str, '&');
    }

    /**
     * 获取支付宝交易的access_token
     * @param int $orderNum 订单号
     * @param string $productName 商品名称
     * @param float $price 价格
     * @return string
     */
    private function getAccessToken($orderNum, $productName, $price) {
        //请求业务参数
        $reqData = '<direct_trade_create_req><notify_url>'
            . ALIPAY_DIRECT_WAP_NOTIFY_URL
            . '</notify_url><call_back_url>'
            . ALIPAY_DIRECT_WAP_CALLBACK_URL
            . '</call_back_url><seller_account_name>'
            . ALIPAY_SELLER
            . '</seller_account_name><out_trade_no>'
            . $orderNum
            . '</out_trade_no><subject>'
            . $productName
            . '</subject><total_fee>'
            . $price
            . '</total_fee><merchant_url></merchant_url></direct_trade_create_req>';

        //构造要请求的参数数组
        $paraToken = array(
            "service" => "alipay.wap.trade.create.direct",
            "partner" => ALIPAY_PARTNER,
            "sec_id" => 'MD5',
            "format" => 'xml',
            "v"	=> '2.0',
            "req_id" => $this->_wapPayReqId,
            "req_data" => $reqData,
            "_input_charset" => 'utf-8'
        );

        $paraToken = $this->buildRequestPara($paraToken); //构造带签名的参数数组

        //post请求，获取access_token
        $response = $this->getHttpResponsePOST(self::$_wapPayUrl, $paraToken);
        $response = urldecode($response);

        preg_match('/<request_token>(.*)<\/request_token>/', $response, $match);

        return isset($match[1]) ? $match[1] : '';
    }

    /**
     * 建立请求，以模拟远程HTTP的POST请求方式构造并获取支付宝的处理结果
     * @param string $url 请求地址
     * @param array $para 请求参数数组
     * @param string $inputCharset 字符编码
     * @return string
     */
    private function getHttpResponsePOST($url, $para, $inputCharset = '') {

        if (trim($inputCharset) != '') {
            $url = $url . "_input_charset=" . $inputCharset;
        }

        $curl = curl_init($url);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); //SSL证书认证
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); //严格认证
        curl_setopt($curl, CURLOPT_CAINFO, self::$_wapPayCertPath); //证书路径
        curl_setopt($curl, CURLOPT_HEADER, 0); //过滤HTTP头
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); //显示输出结果
        curl_setopt($curl, CURLOPT_POST, true); //post传输数据
        curl_setopt($curl, CURLOPT_POSTFIELDS, $para); //post传输数据
        $responseText = curl_exec($curl);

        //var_dump( curl_error($curl) ); //如果执行curl过程中出现异常，可打开此开关，以便查看异常内容
        curl_close($curl);

        return $responseText;
    }
}
